package me.dwk.dandan.function;
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this
 * work for additional information regarding copyright ownership. The ASF
 * licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import me.dwk.dandan.function.exception.InvalidVariableException;
import me.dwk.dandan.function.functions.ChangeCase;
import me.dwk.dandan.function.functions.EscapeHtml;
import me.dwk.dandan.function.functions.EscapeXml;
import me.dwk.dandan.function.functions.EvalVarFunction;
import me.dwk.dandan.function.functions.IntSum;
import me.dwk.dandan.function.functions.LongSum;
import me.dwk.dandan.function.functions.Random;
import me.dwk.dandan.function.functions.RandomFromMultipleVars;
import me.dwk.dandan.function.functions.RandomString;
import me.dwk.dandan.function.functions.SimpleVariable;
import me.dwk.dandan.function.functions.SplitFunction;
import me.dwk.dandan.function.functions.TimeShift;
import me.dwk.dandan.function.functions.UnEscape;
import me.dwk.dandan.function.functions.UnEscapeHtml;
import me.dwk.dandan.function.functions.UrlDecode;
import me.dwk.dandan.function.functions.UrlEncode;
import me.dwk.dandan.function.functions.Uuid;
import me.dwk.dandan.function.reflect.ClassPathScanner;

/**
 * CompoundFunction.
 *
 */
public class CompoundVariable implements Function {
	private static final Logger									log				= LoggerFactory.getLogger(CompoundVariable.class);

	private String												rawParameters;

	private static final FunctionParser							functionParser	= new FunctionParser();

	// Created during class init; not modified thereafter
	private static final Map<String, Class<? extends Function>>	functions		= new HashMap<>();

	static {
		Class<?>[] clazzs = { ChangeCase.class,
				EscapeHtml.class,
				EscapeXml.class,
				EvalVarFunction.class,
				IntSum.class,
				LongSum.class,
				Random.class,
				RandomFromMultipleVars.class,
				RandomString.class,
				SplitFunction.class,
				TimeShift.class,
				UnEscape.class,
				UnEscapeHtml.class,
				UrlDecode.class,
				UrlEncode.class,
				Uuid.class };

		// 动态扫描
		Set<Class<?>> functionClasses = new HashSet<>();

		List<Class<?>> scanClasses = new ClassPathScanner("me.dwk.dandan.function.functions", clazz -> !clazz.isInterface()
																											&& !Modifier.isAbstract(clazz.getModifiers())
																											&& Function.class.isAssignableFrom(clazz)).scan();
		functionClasses.addAll(scanClasses);
		Collections.addAll(functionClasses, clazzs);
		functionClasses.stream().forEach(c -> {
			try {
				Function tempFunc = (Function) c.newInstance();
				String referenceKey = tempFunc.getReferenceKey();
				if (referenceKey.length() > 0) {
					functions.put(referenceKey, tempFunc.getClass());
				}
			} catch (InstantiationException | IllegalAccessException e) {
				e.printStackTrace();
			}
		});

		final int functionCount = functions.size();
		if (functionCount == 0) {
			log.warn("Did not find any functions");
		} else {
			log.info("Function count: {}", functionCount);
		}
	}

	private boolean				hasFunction;
	private boolean				isDynamic;

	private String				permanentResults;

	private LinkedList<Object>	compiledComponents	= new LinkedList<>();

	// static {
	// try {
	// List<String> classes = ClassFinder.findClassesThatExtend(new Class[] {
	// Function.class }, true);
	// for (String clazzName : classes) {
	// Function tempFunc = (Function) Class.forName(clazzName).newInstance();
	// String referenceKey = tempFunc.getReferenceKey();
	// if (referenceKey.length() > 0) {
	// functions.put(referenceKey, tempFunc.getClass());
	// }
	// }
	//
	// final int functionCount = functions.size();
	// if (functionCount == 0) {
	// log.warn("Did not find any functions");
	// } else {
	// log.debug("Function count: {}", functionCount);
	// }
	// } catch (Exception err) {
	// log.error("Exception occurred in static initialization of CompoundVariable.",
	// err);
	// }
	// }

	public CompoundVariable() {
		hasFunction = false;
	}

	public CompoundVariable(String parameters) {
		this();
		try {
			setParameters(parameters);
		} catch (InvalidVariableException e) {
			log.info("Invalid variable: {}", parameters, e);
		}
	}

	/**
	 * Allows the retrieval of the original String prior to it being compiled.
	 *
	 * @return String
	 */
	public String getRawParameters() {
		return rawParameters;
	}

	@Override
	public String execute() {
		if (isDynamic || permanentResults == null) {

			if (compiledComponents == null || compiledComponents.size() == 0)
				return ""; // $NON-NLS-1$

			StringBuilder results = new StringBuilder();
			for (Object item : compiledComponents) {
				if (item instanceof Function) {
					try {
						results.append(((Function) item).execute());
					} catch (InvalidVariableException e) {
						log.info("Invalid variable: {}", item, e);
					}
				} else if (item instanceof SimpleVariable) {
					results.append(((SimpleVariable) item).toString());
				} else {
					results.append(item);
				}
			}
			if (!isDynamic) {
				permanentResults = results.toString();
			}
			return results.toString();
		}
		return permanentResults; // $NON-NLS-1$
	}

	@SuppressWarnings("unchecked") // clone will produce correct type
	public CompoundVariable getFunction() {
		CompoundVariable func = new CompoundVariable();
		func.compiledComponents = (LinkedList<Object>) compiledComponents.clone();
		func.rawParameters = rawParameters;
		func.hasFunction = hasFunction;
		func.isDynamic = isDynamic;
		return func;
	}

	@Override
	public List<String> getArgumentDesc() {
		return new LinkedList<>();
	}

	public void clear() {
		// TODO should this also clear isDynamic, rawParameters, permanentResults?
		hasFunction = false;
		compiledComponents.clear();
	}

	public void setParameters(String parameters) throws InvalidVariableException {
		rawParameters = parameters;
		if (parameters == null || parameters.length() == 0)
			return;

		compiledComponents = functionParser.compileString(parameters);
		if (compiledComponents.size() > 1 || !(compiledComponents.get(0) instanceof String)) {
			hasFunction = true;
		}
		permanentResults = null; // To be calculated and cached on first execution
		isDynamic = false;
		for (Object item : compiledComponents) {
			if (item instanceof Function || item instanceof SimpleVariable) {
				isDynamic = true;
				break;
			}
		}
	}

	static Object getNamedFunction(String functionName) throws InvalidVariableException {
		if (functions.containsKey(functionName)) {
			try {
				return ((Class<?>) functions.get(functionName)).newInstance();
			} catch (Exception e) {
				log.error("Exception occurred while instantiating a function: {}", functionName, e); // $NON-NLS-1$
				throw new InvalidVariableException(e);
			}
		}
		return new SimpleVariable(functionName);
	}

	// For use by FunctionHelper
	public static Class<? extends Function> getFunctionClass(String className) {
		return functions.get(className);
	}

	// For use by FunctionHelper
	public static String[] getFunctionNames() {
		return functions.keySet().toArray(new String[functions.size()]);
	}

	public boolean hasFunction() {
		return hasFunction;
	}

	// Dummy methods needed by Function interface

	/** {@inheritDoc} */
	@Override
	public String getReferenceKey() {
		return ""; // $NON-NLS-1$
	}

	/** {@inheritDoc} */
	@Override
	public void setParameters(Collection<CompoundVariable> parameters) throws InvalidVariableException {}

	public static void main(String[] args) throws InvalidVariableException {
		CompoundVariable cv = new CompoundVariable();
		cv.setParameters("${__P(host,)}");
	}
}
